package afcp.alumni.dao.base;

import java.util.LinkedList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

import afcp.alumni.model.base.DatastoreEntity;

public abstract class DaoDatastoreJPA<T extends DatastoreEntity> extends DaoDatastoreCommon<T> {
	
	private static final Log logger = LogFactory.getLog(DaoDatastoreJPA.class);
	
	protected static final EntityManagerFactory emfInstance = Persistence
			.createEntityManagerFactory("transactions-optional");
	protected static final EntityManager entityManager = emfInstance
			.createEntityManager();
	
//	protected DaoDatastoreJPA() {
//	}

	@Deprecated
	protected void persist(T entity) {
		if (entity != null) {
			EntityTransaction tx = null;
//			EntityManager entityManager = emfInstance.createEntityManager();
			try {
				tx = entityManager.getTransaction();
				tx.begin();
				entityManager.persist(entity);
				entityManager.flush();
				tx.commit();
			} catch (Exception e) {
				logger.error("Persist failed. Rollback...", e);
				if (tx != null && tx.isActive())
					tx.rollback();
			} finally {
//				entityManager.close();
			}
		}
	}

	public List<T> findAll() {
		TypedQuery<T> query = entityManager.createQuery(
				"SELECT e FROM " + this.className + " e", this.getClassObject());
		List<T> entitiesFound = query.getResultList();
		// TODO temp : to update members in database
//		for (T entity : entitiesFound) {
////			if (entity instanceof Member) {
////				Member member = (Member) entity;
//				entity.setId(entity.getKey().getName());
//				EntityTransaction tx = entityManager.getTransaction();
//				tx.begin();
//				entityManager.flush();
//				tx.commit();
////			}
//		}
		// end temp
		return entitiesFound;
	}


	/**
	 * <p>
	 * Find and return the entity with the given id (the number form).
	 * </p>
	 * <p>
	 * Warning: The returned entity is detached. For instance, it can't be used
	 * to remove it from the datastore.
	 * </p>
	 * 
	 * @deprecated
	 * @param id
	 * @return
	 */
	public T find(long id) {
		logger.info("Searching for entity " + this.className + " with id: " + id);
		Key key = KeyFactory.createKey(getAncestorKey(), this.className, id);
//		EntityManager entityManager = emfInstance.createEntityManager();
		T entity = entityManager.find(this.getClassObject(), key);
//		entityManager.close();
		return entity;
	}

	public T find(String id) {
		logger.info("Searching for entity " + this.className + " with id: " + id);
		Key key = KeyFactory.createKey(getAncestorKey(), this.className, id);
//		EntityManager entityManager = emfInstance.createEntityManager();
		T entity = entityManager.find(this.getClassObject(), key);
//		entityManager.close();
		return entity;
	}
	
	public List<T> find(List<String> ids) {
		logger.info("Searching for entity " + this.className + " with ids: " + ids);
//		EntityManager entityManager = emfInstance.createEntityManager();
		List<T> entities = new LinkedList<>();
		for (String id : ids) {
			Key key = KeyFactory.createKey(getAncestorKey(), this.className, id);
			T entity = entityManager.find(this.getClassObject(), key);
			entities.add(entity);
		}
//		entityManager.close();
		return entities;
	}
	
	public boolean exists(T entity) {
		logger.info("Checking if entity " + this.className + " with id: " + entity.getId() + " exists.");
		setKey(entity);
//		String id = entity.getId();
//		T oldEntity = entityManager.find(this.getClassObject(),
//				KeyFactory.createKey(getAncestorKey(), this.className, id));
		T oldEntity = entityManager.find(this.getClassObject(), entity.getKey());
		boolean exists = oldEntity != null;
		if (exists) {
			logger.info("entity " + this.className + " with id: " + oldEntity.getId() + " and key: " + oldEntity.getKey() + " already exists.");
		}
		return exists;
	}

	public boolean addOrUpdate(T entity) {
		if (entity != null) {
			setKey(entity);
//			EntityTransaction tx = null;
//			EntityManager entityManager = emfInstance.createEntityManager();
			
//			String id = entity.getId();
//			try {
//				T oldEntity = entityManager.find(this.getClassObject(),
//						KeyFactory.createKey(this.className, id));
				if (exists(entity)) {
					logger.info("An entity " + this.className + " already exists in database with this id: " + entity.getId());
					
//					// debug code because a member was found sometimes even after it was deleted. The application must be restarted to unlock the locked Id...
//					if (oldEntity instanceof Member) {
//						logger.error("Member");
//						Member member = (Member)oldEntity;
//						logger.error("Id:"+member.getId() + " / firstName:" + member.getFirstName() + " / lastName:" + member.getLastName() + " / password:" + member.getPassword());
//					}
					
				} else {
					logger.info("Add entity " + this.className + " with id: " + entity.getId());
//					tx = entityManager.getTransaction();
//					tx.begin();
					entityManager.persist(entity);
//					entityManager.flush();
//					tx.commit();
					return true;
				}
//			} catch (Exception e) {
//				logger.error("Persist failed. Rollback...", e);
//				if (tx != null && tx.isActive())
//					tx.rollback();
//			}
		} else {
			logger.error("Can't add null entity " + this.className + " to database.");
		}
		return false;
	}
	
	public boolean addOrUpdate(List<T> entities) {
//		EntityTransaction tx = null;
//		try {
//			tx = entityManager.getTransaction();
//			tx.begin();
			for (T entity : entities) {
//				if (exists(entity)) {
//					logger.info("An entity already exists in database with this id: " + entity.getId());
//				} else {
//					entityManager.persist(entity);
//				}
				this.addOrUpdate(entity);
			}
//			entityManager.flush();
//			tx.commit();
			return true;
//		} catch (Exception e) {
//			logger.error("Persist failed. Rollback...", e);
//			if (tx != null && tx.isActive())
//				tx.rollback();
//		}
//		return false;
	}

//	public T remove(T entity) {
//		if (entity != null) {
//			EntityTransaction tx = null;
//			EntityManager em = emfInstance.createEntityManager();
//			try {
//				tx = em.getTransaction();
//				tx.begin();
//				em.remove(entity);
//				tx.commit();
//			} catch (Exception e) {
//				logger.error("Deletion failed. Rollback...", e);
//				if(tx != null && tx.isActive()) tx.rollback();
//			} finally {
//				em.close();
//			}
//		}
//		return entity;
//	}

	public T removeFromKeyString(String id) {
		logger.info("Remove entity " + this.className + " with id: " + id);
		// return this.remove(this.find(id));
		T entity = null;
//		EntityTransaction tx = null;
//		EntityManager entityManager = emfInstance.createEntityManager();
//		try {
//			tx = entityManager.getTransaction();
//			tx.begin();
		
//			entity = entityManager.find(this.getClassObject(),
//					KeyFactory.createKey(getAncestorKey(), this.className, id));
//			entityManager.remove(entity);
			

			try {
				TypedQuery<T> query = entityManager.createQuery(
						"DELETE FROM " + this.className + " e WHERE e.id = :id", this.getClassObject());
				query.setParameter("id", id);
				query.executeUpdate();
			} catch (Exception e) {
				logger.error("An error occured while deleting the entity " + this.className + " with id: " + id, e);
				return null;
			}
			
//			em.persist(entity);
//			entityManager.flush();
//			tx.commit();
//		} catch (Exception e) {
//			logger.error("Deletion failed. Rollback...", e);
//			if (tx != null && tx.isActive())
//				tx.rollback();
//		}
		return entity;
	}
	
	public boolean remove(List<String> ids) {
//		if (logger.isInfoEnabled()) {
//			StringBuilder builder = new StringBuilder();
//			builder.append("Liste des entités supprimées : ");
//			for (T entity : entities) {
//				builder.append(entity.getId()).append(", ");
//			}
//			logger.info(builder);
//		}
		
		// TODO may be optimized with a single query removing all entities in one go.
//		EntityTransaction tx = null;
//		try {
//			tx = entityManager.getTransaction();
//			tx.begin();
			for (String id : ids) {
//				entity = entityManager.find(this.getClassObject(), 
//						KeyFactory.createKey(this.className, entity.getId()));
//				// The point: the remove() method is called once for each entity.
//				entityManager.remove(entity);
				if (this.removeFromKeyString(id) == null) {
					return false;
				}
			}
//			entityManager.flush();
			return true;
//			tx.commit();
//			entityManager.flush();
//		} catch (Exception e) {
//			logger.error("Deletion failed. Rollback...", e);
//			if (tx != null && tx.isActive())
//				tx.rollback();
//		}
//		return false;
		
		// Example of optimization (to adapt):
//		@Override
//		public void deleteAllEmployees() {
//			transactionTemplate.execute(new TransactionCallback(){
//				@Override
//				public Object doInTransaction(TransactionStatus status) {
//					Query q = em.createQuery ("DELETE FROM Employee");
//					int deleted = q.executeUpdate ();
//					return null;
//				}});
//		}
	}
	
	public boolean removeAll() {
		logger.info("will remove entities " + this.className);
//		try {
//			TypedQuery<T> q = entityManager.createQuery("DELETE FROM " + this.className + " m", this.getClassObject());
//			q.executeUpdate();
//		} catch (Exception e) {
//			logger.error("An error occured while trying to delete all entities of class " + this.className, e);
//		}
		List<T> entities = findAll();
		try {
			for (T entity : entities) {
				T entityInDatastore = entityManager.find(this.getClassObject(), 
						KeyFactory.createKey(this.getAncestorKey(), this.className, entity.getId()));
				// The point: the remove() method is called once for each entity.
				if (entityInDatastore != null) {
					logger.info("remove entity " + this.className + " with id: " + entityInDatastore.getId());
					entityManager.remove(entityInDatastore);
				} else {
					logger.info("entity " + this.className + " with id: " + entity.getId() + " not found in datastore.");
				}
			}
		} catch (Exception e) {
			logger.error("An error occurred while trying to delete all entities " + this.className + ".", e);
			return false;
		}
		
		return true;
	}

	public boolean update(T entity) {
		logger.info("Update entity " + this.className + " with id: " + entity.getId());
		//TODO code à tester, c'est pas sûr que ça fonctionne, en particulier avec les variables
		// qui n'ont pas été définies pendant cette session.
//		emfInstance.createEntityManager().flush();
		entityManager.flush();
		return true;
	}

}
